Skip to content

[Feat-T3-104] 마이페이지 UI 구현#22

Merged
taipaise merged 5 commits into
developfrom
feat/mypage
Jul 21, 2025
Merged

[Feat-T3-104] 마이페이지 UI 구현#22
taipaise merged 5 commits into
developfrom
feat/mypage

Conversation

@taipaise

@taipaise taipaise commented Jul 20, 2025

Copy link
Copy Markdown
Collaborator

🌁 Background

  • 마이페이지 화면 구현

📱 Screenshot

iPhone SE3 iPhone 13 mini iPhone 16 Pro
se3 13mini 16pro

👩‍💻 Contents

  • 마이페이지 화면 구현
  • 유저의 정보 (닉네임, accessToken)을 가져올 수 있는 repository 구현
  • NSObject의 class이름을 String으로 가져올 수 있는 extension 추가

📝 Review Note

  • 짧은 회의에서 말씀 드린대로, UseCase가 단순 데이터 바이패싱하는 것을 지양하기 위해 ViewModel에서 Repository를 직접 참조하도록 구현했습니다. (요 부분은 추후 다른 개발자 분들의 의견과, 추가적인 토론을 통해 결정되는 내용으로 수정하겠습니다)
  • UserDataRepository가 할 수 있는 역할을 러프하게 닉네임 가져오기 / accessToken 가져오기만 protocol에 추가해두었습니다.
    • 해당 레포가 할 수 있는 역할에 대해서는 조금 더 논의해보면 좋을 것 같습니다!

📬 Reference

Summary by CodeRabbit

  • 신규 기능

    • 마이페이지 화면 및 뷰모델이 추가되어 닉네임 표시, 메뉴(목표 재설정, 공지사항, FAQ) 제공, 외부 링크 연결 기능이 제공됩니다.
    • 마이페이지용 커스텀 셀(우측 화살표 아이콘 포함)이 추가되었습니다.
    • 설정 아이콘, 우측 화살표 아이콘이 앱 리소스에 추가되었습니다.
    • 사용자 데이터 저장소 프로토콜과 구현체가 추가되어 액세스 토큰과 닉네임 로드 기능이 강화되었습니다.
  • 리팩터링/기타

    • NSObject에 클래스명 반환 프로퍼티가 추가되었습니다.
    • 의존성 주입 컨테이너에 관련 뷰모델 및 저장소가 등록되었습니다.
    • 탭바 뷰에서 마이페이지 뷰가 의존성 주입을 통해 초기화되도록 변경되었습니다.

@taipaise
taipaise requested a review from choijungp July 20, 2025 14:27
@taipaise taipaise self-assigned this Jul 20, 2025
@coderabbitai

coderabbitai Bot commented Jul 20, 2025

Copy link
Copy Markdown
## Walkthrough

이번 변경에서는 마이페이지 화면 및 관련 기능을 전면적으로 도입하였습니다. 사용자 데이터 저장소 프로토콜 및 구현체, 에러 타입, 뷰모델, 뷰, 테이블뷰 셀 등이 새로 추가되었으며, DI 컨테이너에 필요한 의존성 등록도 이루어졌습니다. 아이콘 리소스와 공통 유틸리티 확장도 포함됩니다.

## Changes

| 파일/경로                                                                                              | 변경 요약                                                                                             |
|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------|
| .../DataSourceDependencyAssembler.swift<br>.../PresentationDependencyAssembler.swift                   | DI 컨테이너에 UserDataRepositoryProtocol 및 MypageViewModel 등록 로직 추가                             |
| .../Error/UserError.swift                                                                              | UserError enum 신설, 사용자 관련 에러 및 한글 설명 제공                                                |
| .../Repository/UserDataRepository.swift                                                                | UserDataRepository 클래스 및 사용자 데이터 로딩 메서드 추가                                            |
| .../Protocol/Repository/UserDataRepositoryProtocol.swift                                               | UserDataRepositoryProtocol 프로토콜 신설, 토큰/닉네임 로딩 메서드 선언                                 |
| .../Images.xcassets/chevron_right_icon.imageset/Contents.json<br>.../setting_icon.imageset/Contents.json | chevron_right_icon, setting_icon 이미지셋 리소스 추가                                                  |
| .../DesignSystem/BitnagilIcon.swift                                                                    | BitnagilIcon에 settingIcon, chevronRightIcon static 프로퍼티 추가                                      |
| .../Extension/NSObject+.swift                                                                          | NSObject에 className 인스턴스/타입 프로퍼티 확장                                                       |
| .../TabBarView.swift                                                                                   | DI에서 MypageViewModel 주입, MypageView 파라미터화 및 기존 MypageView 클래스 정의 제거                 |
| .../MyPage/View/Component/MypageTableViewCell.swift                                                    | MypageTableViewCell 커스텀 셀 클래스 추가, 타이틀/체브론 이미지 포함                                   |
| .../MyPage/View/MypageView.swift                                                                       | MypageView 뷰 컨트롤러 신설, UI 구성 및 뷰모델 바인딩, 테이블뷰/이벤트 처리 구현                      |
| .../MyPage/ViewModel/MypageViewModel.swift                                                             | MypageViewModel 클래스 신설, 메뉴 선택 처리 및 닉네임/외부URL 퍼블리셔 제공                            |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User as 사용자
    participant MypageView as MypageView
    participant MypageViewModel as MypageViewModel
    participant UserDataRepo as UserDataRepository

    User->>MypageView: 메뉴 선택(예: FAQ)
    MypageView->>MypageViewModel: action(input: .didSelectMenu(menu))
    MypageViewModel->>MypageViewModel: handleMenuSelection(menu)
    alt FAQ/Notice 선택
        MypageViewModel->>MypageView: externalURLPublisher로 URL 발행
        MypageView->>User: 외부 브라우저로 URL 오픈
    else resetGoal 선택
        MypageView->>DIContainer: OnboardingViewModel resolve
        MypageView->>User: 온보딩 화면으로 이동
    end

    Note over MypageView: 최초 진입 시
    MypageView->>MypageViewModel: 닉네임 요청
    MypageViewModel->>UserDataRepo: loadNickname()
    UserDataRepo-->>MypageViewModel: 닉네임 반환/에러
    MypageViewModel->>MypageView: nickNamePublisher로 닉네임 발행
    MypageView->>User: 닉네임 표시

Estimated code review effort

1 (<30 minutes)

Poem

🥕
마이페이지 토끼가 깡총깡총,
새 아이콘과 셀, 닉네임도 뿅!
저장소에서 토큰과 이름을 찾아,
뷰모델과 뷰가 함께 춤춰요.
메뉴를 누르면 외부로 점프,
오늘도 변화는 당근처럼 달콤! 🐇✨


<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details>
<summary>🧪 Generate unit tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post copyable unit tests in a comment
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `feat/mypage`

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=YAPP-Github/Bitnagil-iOS&utm_content=22):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` or `@coderabbitai 요약` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (11)
Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (2)

13-13: 오타 수정이 필요합니다.

titleLableLeadingSpacing에서 titleLabelLeadingSpacing으로 수정해주세요.

-        static let titleLableLeadingSpacing: CGFloat = 20
+        static let titleLabelLeadingSpacing: CGFloat = 20

53-53: Layout 상수명 오타로 인한 불일치

위에서 수정된 Layout 상수명과 일치시켜주세요.

-            make.leading.equalToSuperview().offset(Layout.titleLableLeadingSpacing)
+            make.leading.equalToSuperview().offset(Layout.titleLabelLeadingSpacing)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (1)

20-27: TODO 주석에 대한 후속 조치가 필요합니다.

정적 분석 도구에서도 지적했듯이 accessToken fetch 로직에 대한 TODO가 남아있습니다. 팀 회의를 통해 결정된 후 이 주석을 제거하거나 구현을 업데이트해주세요.

현재 구현 자체는 올바르게 작동하지만, 향후 변경될 가능성이 있는 부분이라는 점을 팀원들과 공유하는 것이 좋겠습니다.

Projects/Presentation/Sources/MyPage/View/MypageView.swift (5)

44-46: 불필요한 메서드 오버라이드 제거 필요

BaseViewController의 viewDidLoad()에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, super.viewDidLoad() 호출만으로 충분합니다. 현재 오버라이드는 추가 로직이 없어 불필요합니다.

-    override func viewDidLoad() {
-        super.viewDidLoad()
-    }

60-60: 임시 배경색 처리 개선 제안

프로필 이미지뷰의 배경색이 임시로 설정되어 있습니다. 실제 프로필 이미지 로딩 로직이나 기본 플레이스홀더 이미지 처리를 고려해보세요.


147-158: 테이블뷰 셀 구성 로직 개선

guard 문에서 실패 시 빈 셀을 반환하는 대신, 적절한 에러 처리나 로깅을 추가하는 것이 좋습니다.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard
            let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell
-        else { return .init() }
+        else { 
+            BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell")
+            return UITableViewCell()
+        }

        let title = MypageViewModel.MypageMenu
            .allCases[indexPath.row]
            .rawValue
        cell.configure(title: title)

        return cell
    }

44-46: 불필요한 viewDidLoad 오버라이드 제거를 고려해보세요

Static analysis에서 지적한 대로, 이 메서드는 super.viewDidLoad()만 호출하고 있어 불필요합니다. Retrieved learning에 따르면 BaseViewController의 viewDidLoad()에서 이미 필요한 메서드들(configureAttribute, configureLayout, bind)을 호출하므로 이 오버라이드는 제거할 수 있습니다.

다음과 같이 메서드를 제거할 수 있습니다:

-    override func viewDidLoad() {
-        super.viewDidLoad()
-    }

60-60: 임시 배경색에 대한 TODO 주석 추가를 고려해보세요

프로필 이미지뷰의 배경색이 임시로 설정되어 있다는 주석이 있습니다. 향후 실제 프로필 이미지 로직이 구현될 때 놓치지 않도록 TODO 주석을 추가하는 것을 권장합니다.

-        profileImageView.backgroundColor = BitnagilColor.gray40 // 임시
+        profileImageView.backgroundColor = BitnagilColor.gray40 // TODO: 실제 프로필 이미지 로직 구현 필요
Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (3)

58-59: resetGoal 메뉴의 처리 로직 명확화 필요

현재 resetGoal 메뉴는 빈 break문으로 처리되어 있습니다. View에서 별도로 처리한다는 의도인지 명확하게 주석으로 표시하거나, 일관성을 위해 ViewModel에서 처리하는 것을 고려해보세요.

        case .resetGoal:
-            break
+            // View에서 직접 네비게이션 처리
+            break

58-59: resetGoal 케이스 처리 로직 명확화가 필요합니다

현재 resetGoal 케이스는 break만 하고 있어서 실제로는 아무것도 하지 않습니다. 이 케이스는 뷰에서 직접 처리하고 있으니, 주석으로 이를 명확히 해주거나 다른 방식으로 처리하는 것을 고려해보세요.

        case .resetGoal:
-            break
+            break // 뷰에서 직접 온보딩으로 네비게이션 처리

60-67: 임시 URL을 상수로 관리하는 것을 권장합니다

하드코딩된 임시 URL들을 별도 상수로 관리하거나, 향후 실제 API 엔드포인트로 교체할 때를 대비해 설정 가능한 구조로 만드는 것을 고려해보세요.

클래스 상단에 URL 상수들을 추가할 수 있습니다:

final class MypageViewModel: ViewModel {
+    private enum ExternalURL {
+        static let notice = "https://www.google.com" // TODO: 실제 공지사항 URL로 변경
+        static let faq = "https://www.naver.com" // TODO: 실제 FAQ URL로 변경
+    }
+
    enum Input {
        case didSelectMenu(menu: MypageMenu)
    }

그리고 handleMenuSelection에서:

        case .notice: // 임시 url
-            if let url = URL(string: "https://www.google.com") {
+            if let url = URL(string: ExternalURL.notice) {
                externalURLPublisher.send(url)
            }
        case .faq: // 임시 url
-            if let url = URL(string: "https://www.naver.com") {
+            if let url = URL(string: ExternalURL.faq) {
                externalURLPublisher.send(url)
            }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 211de3a and d7047ca.

⛔ Files ignored due to path filters (6)
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@1x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1 hunks)
  • Projects/DataSource/Sources/Common/Error/UserError.swift (1 hunks)
  • Projects/DataSource/Sources/Repository/UserDataRepository.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift (1 hunks)
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json (1 hunks)
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json (1 hunks)
  • Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1 hunks)
  • Projects/Presentation/Sources/Common/Extension/NSObject+.swift (1 hunks)
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1 hunks)
  • Projects/Presentation/Sources/Common/TabBarView.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/View/MypageView.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/Common/TabBarView.swift (1)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (2)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (3)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🧬 Code Graph Analysis (5)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
  • register (14-16)
  • resolve (18-25)
Projects/Presentation/Sources/Common/TabBarView.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • resolve (18-25)
Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift (1)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (2)
  • loadAccessToken (21-27)
  • loadNickname (29-35)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • register (14-16)
Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1)
Projects/Shared/Sources/Logger/Logger.swift (1)
  • log (13-22)
🪛 SwiftLint (0.57.0)
Projects/DataSource/Sources/Repository/UserDataRepository.swift

[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)

(todo)

Projects/Presentation/Sources/MyPage/View/MypageView.swift

[Warning] 44-44: Remove overridden functions that don't do anything except call their super

(unneeded_override)

🔇 Additional comments (22)
Projects/Presentation/Sources/Common/Extension/NSObject+.swift (2)

10-18: 간결하고 재사용성 높은 유틸리티 👍
인스턴스/타입 양쪽에서 모두 사용할 수 있게 정의한 점이 유용합니다. 추가 의견 없이 승인드립니다.


10-18: 저장소 내 NSObject 확장 className 중복 정의 없음 확인

  • 확인 파일: Projects/Presentation/Sources/Common/Extension/NSObject+.swift
    • var className: String { … } (11행)
    • class var className: String { … } (15행)
  • 다른 파일에서 동일 시그니처를 찾을 수 없음

서드파티/사내 모듈(RxSwift, UIKit 등)에 동일 확장 정의가 있는지 별도 검토 부탁드립니다.

Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json (1)

1-23: 표준 애셋 카탈로그 구조로 올바르게 구현됨

Xcode의 표준 이미지 애셋 카탈로그 구조를 정확히 따르고 있으며, 1x, 2x, 3x 스케일 변형을 모두 포함하고 있습니다.

Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json (1)

1-23: 표준 애셋 카탈로그 구조로 올바르게 구현됨

Xcode의 표준 이미지 애셋 카탈로그 구조를 정확히 따르고 있으며, 모든 스케일 변형을 포함하고 있습니다.

Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1)

32-34: 새로운 아이콘이 일관된 패턴으로 추가됨

기존 아이콘들과 동일한 번들 해상도 방식과 명명 규칙을 따라 구현되어 있어 일관성이 유지되고 있습니다.

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)

39-44: ✅ UserDataRepositoryProtocol DI 등록 완료

DataSource 레이어의 DataSourceDependencyAssembler.swift에서 UserDataRepositoryProtocol이 올바르게 등록되어 있어, Presentation 레이어의 MypageViewModel 의존성 해결에 문제가 없습니다. 추가 확인이나 조치가 필요 없습니다.

  • Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
    DIContainer.shared.register(type: UserDataRepositoryProtocol.self) { _ in UserDataRepository(...) }
Projects/Presentation/Sources/Common/TabBarView.swift (2)

41-44: 의존성 주입이 올바르게 구현됨

MypageViewModel의 의존성 해상도가 프로젝트의 일관된 패턴을 따르고 있으며, 오류 처리도 적절합니다.


49-49: ViewModel 주입으로 MVVM 아키텍처 적용

MypageView가 ViewModel을 생성자를 통해 주입받도록 변경되어 적절한 의존성 주입 패턴을 따르고 있습니다.

Projects/DataSource/Sources/Common/Error/UserError.swift (1)

8-24: 에러 처리 구현이 잘 되어 있습니다.

ErrorCustomStringConvertible 프로토콜을 적절히 구현하고, 각 에러 케이스에 대한 명확한 한국어 메시지를 제공합니다. unknown 케이스에서 원본 에러의 localizedDescription을 포함하는 것도 디버깅에 도움이 될 것입니다.

Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)

41-43: 의존성 주입 등록이 올바르게 구현되었습니다.

기존의 다른 Repository 등록 패턴과 일관성 있게 구현되었으며, 필요한 storage 의존성들을 올바르게 주입하고 있습니다.

Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift (1)

8-17: 프로토콜 설계가 깔끔하고 명확합니다.

사용자 데이터 로딩에 필요한 메서드들이 잘 정의되어 있으며, 한국어 문서화도 충실합니다. throws를 사용하여 실패 케이스를 적절히 처리할 수 있도록 설계된 점이 좋습니다.

Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (1)

11-57: 테이블뷰 셀 구현이 잘 되어 있습니다.

SnapKit을 활용한 Auto Layout 설정과 Layout enum을 통한 상수 관리, 디자인 시스템 활용 등이 프로젝트의 기존 패턴과 일관성 있게 구현되었습니다.

Projects/DataSource/Sources/Repository/UserDataRepository.swift (1)

11-36: Repository 구현이 프로토콜 명세를 잘 따르고 있습니다.

적절한 storage 추상화를 사용하고, 실패 시 명확한 에러를 던지도록 구현되어 있습니다. 의존성 주입을 통한 초기화도 깔끔합니다.

Projects/Presentation/Sources/MyPage/View/MypageView.swift (4)

13-25: 레이아웃 상수 구성이 잘 되어있음

Layout enum을 통한 상수 관리가 깔끔하게 구성되어 있고, 코드 가독성과 유지보수성을 향상시킵니다.


83-116: SnapKit을 활용한 Auto Layout 구성이 적절함

제약 조건 설정이 명확하고 일관성 있게 구성되어 있습니다. safeArea를 적절히 활용하여 다양한 화면 크기에 대응할 수 있도록 구현되었습니다.


12-25: Layout 상수 관리가 잘 구조화되어 있습니다

private enum을 사용하여 레이아웃 상수들을 체계적으로 관리하고 있어 유지보수성이 좋습니다. 특히 profileImageViewCornerRadiusprofileImageViewSize의 절반으로 계산되는 것도 일관성 있게 처리되어 있습니다.


35-42: 초기화 메서드 구현이 적절합니다

DI를 통한 ViewModel 주입과 Combine의 cancellables 초기화가 올바르게 구현되어 있습니다. Storyboard 초기화를 막는 fatalError 처리도 적절합니다.

Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (5)

41-46: 초기화 시 에러 처리가 적절함

사용자 데이터 로딩 실패 시 로그를 남기면서도 앱이 계속 동작하도록 구현된 것이 좋습니다. 닉네임 로딩 실패 시에도 UI가 정상적으로 표시됩니다.


23-27: MypageMenu enum 구성이 깔끔함

String 원시값을 가진 CaseIterable enum으로 메뉴 항목을 관리하는 방식이 깔끔하고, UI에서 사용하기에 적합합니다.


37-39: Output 구조체를 통한 퍼블리셔 관리가 적절함

AnyPublisher를 사용하여 구현 세부사항을 숨기고, Output 구조체로 퍼블리셔들을 그룹핑한 것이 MVVM 패턴에 잘 맞습니다.


13-27: Input/Output 패턴과 메뉴 구조가 잘 설계되어 있습니다

  • Input/Output 패턴으로 뷰모델의 인터페이스가 명확하게 정의되어 있습니다
  • MypageMenu enum을 CaseIterable로 구현하여 테이블뷰에서 활용하기 좋게 구조화되어 있습니다
  • 한국어로 된 rawValue가 UI에 직접 사용될 수 있어 편리합니다

30-31: Subject 타입 선택이 적절합니다

  • 닉네임은 CurrentValueSubject로 초기값과 최신 상태를 유지할 수 있도록 했고
  • 외부 URL은 PassthroughSubject로 일회성 이벤트를 처리하도록 한 것이 적절한 선택입니다

Comment on lines +126 to +113
viewModel.output.externalURLPublisher
.sink { url in
UIApplication.shared.open(url)
}
.store(in: &cancellables)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

외부 URL 열기 시 에러 처리 추가 필요

UIApplication.shared.open(url)이 실패할 가능성을 고려하여 에러 처리를 추가하는 것이 좋습니다.

        viewModel.output.externalURLPublisher
            .sink { url in
-                UIApplication.shared.open(url)
+                UIApplication.shared.open(url) { success in
+                    if !success {
+                        BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)")
+                    }
+                }
            }
            .store(in: &cancellables)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewModel.output.externalURLPublisher
.sink { url in
UIApplication.shared.open(url)
}
.store(in: &cancellables)
viewModel.output.externalURLPublisher
.sink { url in
- UIApplication.shared.open(url)
+ UIApplication.shared.open(url) { success in
+ if !success {
+ BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)")
+ }
+ }
}
.store(in: &cancellables)
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 126
to 130, the call to UIApplication.shared.open(url) does not handle potential
failures. Modify the code to use the version of
open(_:options:completionHandler:) that provides a completion handler, and add
error handling logic inside the completion handler to properly manage cases
where opening the URL fails.

🛠️ Refactor suggestion

외부 URL 열기 시 에러 처리 추가를 권장합니다

UIApplication.shared.open(url)이 실패할 수 있는 경우가 있으므로 에러 처리를 추가하는 것이 좋겠습니다.

-        viewModel.output.externalURLPublisher
-            .sink { url in
-                UIApplication.shared.open(url)
-            }
-            .store(in: &cancellables)
+        viewModel.output.externalURLPublisher
+            .sink { url in
+                UIApplication.shared.open(url) { success in
+                    if !success {
+                        BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)")
+                    }
+                }
+            }
+            .store(in: &cancellables)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewModel.output.externalURLPublisher
.sink { url in
UIApplication.shared.open(url)
}
.store(in: &cancellables)
viewModel.output.externalURLPublisher
.sink { url in
UIApplication.shared.open(url) { success in
if !success {
BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)")
}
}
}
.store(in: &cancellables)
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 126
to 130, the call to UIApplication.shared.open(url) does not handle potential
failures. Modify the code to use the version of
open(_:options:completionHandler:) that provides a completion handler, and add
error handling logic inside the completion handler to manage cases where opening
the URL fails.

Comment on lines +148 to +150
guard
let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell
else { return .init() }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

테이블뷰 셀 dequeue 실패 시 더 안전한 처리를 권장합니다

현재 빈 UITableViewCell을 반환하고 있지만, 이는 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 셀을 제공하는 것이 좋겠습니다.

        guard
            let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell
-        else { return .init() }
+        else { 
+            BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell")
+            return UITableViewCell()
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
guard
let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell
else { return .init() }
guard
let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell
else {
BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell")
return UITableViewCell()
}
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 148
to 150, the dequeueReusableCell call returns an empty UITableViewCell on
failure, which can cause unexpected UI issues. Instead, implement clearer error
handling by either logging the failure, asserting, or providing a fallback cell
that matches the expected type to ensure UI consistency and easier debugging.

Comment on lines +170 to +159
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fatalError 사용에 대한 검토 필요

의존성이 등록되지 않았을 때 앱이 크래시되는 것보다는, 사용자에게 적절한 에러 메시지를 보여주거나 이전 화면으로 돌아가는 것이 더 나은 UX를 제공할 수 있습니다.

        guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
-            fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
+            BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.")
+            // TODO: 사용자에게 에러 알림 표시
+            return
        }
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 170
to 172, replace the fatalError call used when onboardingViewModel dependency is
not resolved with a more user-friendly error handling approach. Instead of
crashing, implement logic to show an appropriate error message to the user or
navigate back to the previous screen to improve UX when the dependency is
missing.

🛠️ Refactor suggestion

의존성 해결 실패 시 더 우아한 에러 처리를 고려해보세요

fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 대안적인 에러 처리나 fallback 로직을 고려해보시기 바랍니다.

        guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
-            fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
+            BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.")
+            // 적절한 에러 처리나 사용자 알림 로직 추가
+            return
        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
}
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.")
// 적절한 에러 처리나 사용자 알림 로직 추가
return
}
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 170
to 172, replace the fatalError call used when onboardingViewModel dependency
resolution fails with a more graceful error handling approach. Implement
alternative error handling such as returning a default view model, showing an
error message to the user, or safely recovering without crashing the app to
avoid forced termination in production.

Comment on lines +41 to +46
do {
let nickname = try userDataRepository.loadNickname()
nicknamePublisher.send(nickname)
} catch {
BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

초기화 시 에러 처리를 개선할 수 있습니다

현재는 닉네임 로드 실패 시 로그만 남기고 빈 문자열을 유지하고 있습니다. 사용자 경험을 위해 기본값이나 대체 텍스트를 제공하는 것을 고려해보세요.

        do {
            let nickname = try userDataRepository.loadNickname()
            nicknamePublisher.send(nickname)
        } catch {
            BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)")
+            nicknamePublisher.send("사용자") // 기본 닉네임 제공
        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
do {
let nickname = try userDataRepository.loadNickname()
nicknamePublisher.send(nickname)
} catch {
BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)")
}
do {
let nickname = try userDataRepository.loadNickname()
nicknamePublisher.send(nickname)
} catch {
BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)")
nicknamePublisher.send("사용자") // 기본 닉네임 제공
}
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift around
lines 41 to 46, the error handling when loading the nickname only logs the error
but leaves the nickname empty. Improve this by providing a default or fallback
nickname value in the catch block and sending it via nicknamePublisher to ensure
a better user experience.

Comment on lines +60 to +67
case .notice: // 임시 url
if let url = URL(string: "https://www.google.com") {
externalURLPublisher.send(url)
}
case .faq: // 임시 url
if let url = URL(string: "https://www.naver.com") {
externalURLPublisher.send(url)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

하드코딩된 URL을 설정으로 관리 제안

임시 URL들이 하드코딩되어 있습니다. 향후 유지보수를 위해 설정 파일이나 Constants로 관리하는 것을 고려해보세요.

        case .notice: // 임시 url
-            if let url = URL(string: "https://www.google.com") {
+            if let url = URL(string: AppConfig.noticeURL) {
                externalURLPublisher.send(url)
            }
        case .faq: // 임시 url
-            if let url = URL(string: "https://www.naver.com") {
+            if let url = URL(string: AppConfig.faqURL) {
                externalURLPublisher.send(url)
            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case .notice: // 임시 url
if let url = URL(string: "https://www.google.com") {
externalURLPublisher.send(url)
}
case .faq: // 임시 url
if let url = URL(string: "https://www.naver.com") {
externalURLPublisher.send(url)
}
case .notice: // 임시 url
if let url = URL(string: AppConfig.noticeURL) {
externalURLPublisher.send(url)
}
case .faq: // 임시 url
if let url = URL(string: AppConfig.faqURL) {
externalURLPublisher.send(url)
}
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift around
lines 60 to 67, the URLs for .notice and .faq cases are hardcoded. To improve
maintainability, move these URLs to a configuration file or a Constants struct,
then reference those constants here instead of hardcoding the strings directly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (3)

126-130: 외부 URL 열기 시 에러 처리 추가를 권장합니다

UIApplication.shared.open(url)이 실패할 수 있는 경우가 있으므로 에러 처리를 추가하는 것이 좋겠습니다.

-        viewModel.output.externalURLPublisher
-            .sink { url in
-                UIApplication.shared.open(url)
-            }
-            .store(in: &cancellables)
+        viewModel.output.externalURLPublisher
+            .sink { url in
+                UIApplication.shared.open(url) { success in
+                    if !success {
+                        BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)")
+                    }
+                }
+            }
+            .store(in: &cancellables)

148-150: 테이블뷰 셀 dequeue 실패 시 더 안전한 처리를 권장합니다

현재 빈 UITableViewCell을 반환하고 있지만, 이는 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 셀을 제공하는 것이 좋겠습니다.

        guard
            let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell
-        else { return .init() }
+        else { 
+            BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell")
+            return UITableViewCell()
+        }

170-172: 의존성 해결 실패 시 더 우아한 에러 처리를 고려해보세요

fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 대안적인 에러 처리나 fallback 로직을 고려해보시기 바랍니다.

        guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
-            fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
+            BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.")
+            // 적절한 에러 처리나 사용자 알림 로직 추가
+            return
        }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7047ca and 0d00b2f.

⛔ Files ignored due to path filters (6)
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@1x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1 hunks)
  • Projects/DataSource/Sources/Common/Error/UserError.swift (1 hunks)
  • Projects/DataSource/Sources/Repository/UserDataRepository.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift (1 hunks)
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json (1 hunks)
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json (1 hunks)
  • Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1 hunks)
  • Projects/Presentation/Sources/Common/Extension/NSObject+.swift (1 hunks)
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1 hunks)
  • Projects/Presentation/Sources/Common/TabBarView.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/View/MypageView.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json
  • Projects/Presentation/Sources/Common/Extension/NSObject+.swift
  • Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift
  • Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
  • Projects/Presentation/Sources/Common/TabBarView.swift
  • Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift
  • Projects/DataSource/Sources/Common/Error/UserError.swift
  • Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift
  • Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift
🧰 Additional context used
🧠 Learnings (1)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (5)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/MyPage/View/MypageView.swift

[Warning] 44-44: Remove overridden functions that don't do anything except call their super

(unneeded_override)

Projects/DataSource/Sources/Repository/UserDataRepository.swift

[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)

(todo)

🔇 Additional comments (4)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (2)

11-18: 의존성 주입을 통한 깔끔한 구조

저장소 프로토콜들을 의존성 주입으로 받아서 테스트 가능하고 유연한 구조를 만들어주셨네요. 잘 구현되었습니다.


29-36: 일관된 에러 처리 패턴

loadAccessToken과 동일한 패턴으로 구현되어 일관성이 좋고, 적절한 커스텀 에러를 사용해서 에러 핸들링이 잘 되어있습니다.

Projects/Presentation/Sources/MyPage/View/MypageView.swift (2)

48-72: 체계적인 UI 설정 구현

디자인 시스템을 잘 활용하고 있고, UI 컴포넌트들이 체계적으로 설정되어 있습니다. 임시로 설정된 프로필 이미지 배경색도 적절히 주석 처리되어 있네요.


74-117: 깔끔한 레이아웃 구성

SnapKit을 활용한 Auto Layout 설정이 체계적이고, Layout enum의 상수들을 잘 활용해서 가독성과 유지보수성이 좋습니다.

Comment on lines +20 to +27
// TODO: - accessToken fetch 로직 상의 후 결정
func loadAccessToken() throws -> String {
guard
let token = keychainStorage.load(forKey: TokenType.accessToken.rawValue)
else { throw UserError.accessTokenLoadFailed }

return token
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

TODO 주석 해결 필요

SwiftLint에서 TODO 주석에 대한 경고가 발생하고 있습니다. 현재 구현이 적절하다면 TODO를 제거하거나, 실제로 논의가 필요하다면 구체적인 계획을 세우시는 것이 좋겠습니다.

- // TODO: - accessToken fetch 로직 상의 후 결정
+ // 키체인에서 액세스 토큰을 로드

또는 실제로 로직 변경이 필요하다면 이슈를 생성해서 추적하는 것을 고려해보세요.


TODO 주석 정리 필요

SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다.
현재 구현이 확정된 상태라면 해당 주석을 삭제하거나, 실제 의도를 명확히 설명하는 주석으로 변경해주세요.
아직 로직 변경이 논의 중이라면 별도의 이슈를 생성해 추적하는 방안을 권장드립니다.

  • 대상 파일: Projects/DataSource/Sources/Repository/UserDataRepository.swift (20–27행)
  • 예시 변경안:
- // TODO: - accessToken fetch 로직 상의 후 결정
+ // 키체인에서 액세스 토큰을 로드
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: - accessToken fetch 로직 상의 후 결정
func loadAccessToken() throws -> String {
guard
let token = keychainStorage.load(forKey: TokenType.accessToken.rawValue)
else { throw UserError.accessTokenLoadFailed }
return token
}
// 키체인에서 액세스 토큰을 로드
func loadAccessToken() throws -> String {
guard
let token = keychainStorage.load(forKey: TokenType.accessToken.rawValue)
else { throw UserError.accessTokenLoadFailed }
return token
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)

(todo)

🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/UserDataRepository.swift around lines
20 to 27, the TODO comment causes SwiftLint warnings. If the accessToken fetch
logic is finalized, remove the TODO comment entirely or replace it with a clear
explanatory comment. If the logic is still under discussion, create a separate
issue to track it and remove the TODO from the code to avoid lint warnings.

Comment thread Projects/Presentation/Sources/MyPage/View/MypageView.swift Outdated

@choijungp choijungp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

띵동 매우 감사 ... 웰끔입니다 ......... 🥺

Comment thread Projects/DataSource/Sources/Repository/UserDataRepository.swift Outdated
Comment thread Projects/DataSource/Sources/Repository/UserDataRepository.swift Outdated
Comment on lines +11 to +17
var className: String {
return String(describing: type(of: self))
}

class var className: String {
return String(describing: self)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 className 너무너무 좋은 것 같아요 !!
테이블 뷰 셀 정의할 때 하드코딩 안하고 이렇게 하는 고지능적 개발 !!! 감사합니다.

다만 궁금한게 var classNameclass var className가 어떻게 다른지 알 수 있을까요 ??
둘 다 사용해야만 className을 빼올 수 있는건가용 ??

제가 feat/myPage 브랜치에서 다음 부분을 주석처리해도 마이페이지가 잘 작동하는 것 같아 궁금해서 질문 남겨봅니다 !!!

 var className: String {
    return String(describing: type(of: self))
}

추가적으로 이 extension이 public인 이유도 궁금합니다 !!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선 public일 필요는 전혀 없습니다!! 추가의 이뉴는 아시다시피 UIView가 NSObject의 서브 클래스이기 때문에 UIView 서브 클래스들의 클래스 면 사용을 편하게 하기 위해서 인데요!! 때문에 Presentation 모듈 밖에서 사용할 일이 없으니 떼는게 좋을거 같아요!! 진짜 꼼꼼하게 봐주셨군요 감사합니다!!!

두 변수의 차이점은 인스턴스 변수냐, 클래스 변수냐의 차이입니다.

  • var class 의 경우, 인스턴스가 존재할 때 해당 인스턴스의 클래스 이름을 불러올 필요가 있을때 사용하시면 되구
  • class var의 경우, 타입 자체의 이름을 String으로 불러올 때 사용하시면 좋을 것 같습니다!

아직은 두 번째 class var 만 사용하고 있지만, 정말 추후 혹시나 인스턴스의 클래스 이름을 String으로 빼오는 경우도 고려해서 한 번에 추가해 두었습니다!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헐 ~ 바로 이해됩니다 .... 띵천재 띵고수 😎
많이 배워갑니다 ~~

Comment thread Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift Outdated
Comment thread Projects/Presentation/Sources/MyPage/View/MypageView.swift Outdated
Comment thread Projects/Presentation/Sources/MyPage/View/MypageView.swift Outdated
Comment thread Projects/DataSource/Sources/Common/Error/UserError.swift Outdated
Comment on lines +170 to +158
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 부분에 대해서 혼자 진짜 정말정말 많이 고민했는데 아직 잘 모르겠는 부분이 있습니다 ...........

현재 OnboardingView를 재사용하고 있잖아요 ..
그러면서 필요한 OnboardingViewModel을 DIContainer에서 뽑아 쓰게 됩니다 !!


DIContainer의 구현 세부사항을 확인해보면 딕셔너리로 value에 @escaping 클로저를 저장하여
의존성이 정말 필요할 때 해당 인스턴스를 반환해서 사용하고 있습니다 ...

// DIContainer 일부 .. 
private var storage: [String: (DIContainer) -> Any] = [:]

public func resolve<T>(type: T.Type) -> T? {
    guard let instance = storage["\(type)"]?(self) as? T else {
        BitnagilLogger.log(logType: .error, message: "\(type) Resolve Fail: 등록되지 않은 의존성입니다.")
        return nil
    }

    return instance
}

그럼 이미 OnboardingViewModel은 OnboaringView를 처음 생성(ex. 로그인 후 온보딩 진입)할 때 이미 인스턴스가 생성되어 사용되게 되고,
OnboardingView를 재사용할 때(ex. 마이페이지에서 목표 재설정) DIContianer에서 OnboardingViewModel을 꺼내 쓰게 된다면 OnboaringViewModel의 새로운 인스턴스를 생성해서 사용하게 되는 구조가 맞을까요 ??

이건 불가피한 상황인지 .. 어떻게 생각하시나요 ....

이 코멘트로 .... 제 고민이 잘 전달될지 모루겠네요 ㅠㅠ

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성해야하는 의존성이 굉장히 무겁다면 하나의 인스턴스를 DIContainer에 등록해두고, 같은 인스턴스를 재활용해서 사용하는 방법을 고려해보겠지만, 현재 구조도 괜찮지 않을까요?
항상 새로운 인스턴스를 반환하기 때문에 메모리 상에 독립적으로 존재한다는 점도, 단점이 될 수도 있지만, 장점이 될 수도 있다고 생각합니다! 상태를 공유하지 않기 때문에 예상치 못한 동작을 방지할 수도 있으니까요
조이의 의견도 궁금합니다!!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 뷰모델의 경우는 장점이 될 수 있다고 생각하는데 .. 저희 Repository나 UseCase는 따로 상태를 갖고 있는 녀석들은 아닌데 요 부분에서는 약간 단점이 될 수 있을 것 같다는 고민이 됩니다 !!!

하지만 23일 자료에 넣어버릴깝쇼 ??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 생각입니다!! 일단 빠르게 쳐내는 것이 중요하니까요.. 이 부분에 대해서는 다음주에 길게 이야기 나눠보면 좋을거 같습니다!

taipaise added 5 commits July 21, 2025 21:32
- BitnagilIcon에 이미지 추가
- {class이름}.className을 통해 클래스의 이름을 스트링으로 사용할 수 있도록 구현
- 유저 정보 (닉네임, accessToken)을 가져올 수 있는 repository 구현

- UserDataRepository 의존성 등록
- 마이페이지 화면 구현
- 의존성 등록
- TabBar에서 Myapge 클래스 삭제
@taipaise

Copy link
Copy Markdown
Collaborator Author

@choijungp 1ae7ba7 에서 리뷰 주신 내용 반영했습니다. 감사합니다!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (9)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (3)

20-20: TODO 주석 해결 필요

SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 현재 구현이 확정된 상태라면 해당 주석을 삭제하거나, 실제 의도를 명확히 설명하는 주석으로 변경해주세요.


20-20: TODO 주석 해결 필요

SwiftLint에서 TODO 주석으로 인한 경고가 계속 발생하고 있습니다. 현재 구현이 확정되었다면 주석을 제거하거나 구체적인 설명으로 변경해주세요.


20-20: TODO 주석 정리가 필요합니다

SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 현재 구현이 확정되었다면 해당 주석을 제거하거나 명확한 설명으로 변경해주세요.

Projects/Presentation/Sources/MyPage/View/MypageView.swift (6)

134-137: 셀 dequeue 실패 시 더 안전한 처리 필요

현재 빈 UITableViewCell을 반환하고 있지만, 이는 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 로직을 고려해보세요.


157-159: 의존성 해결 실패 시 더 우아한 에러 처리 필요

fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 로깅 후 적절한 fallback 처리나 사용자 알림을 고려해보세요.


135-137: 테이블뷰 셀 dequeue 실패 시 더 안전한 처리가 필요합니다

현재 빈 UITableViewCell을 반환하고 있어 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 셀 제공을 고려해주세요.


157-159: 의존성 해결 실패 시 더 우아한 에러 처리를 고려해주세요

fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 대안적인 에러 처리나 fallback 로직을 고려해보시기 바랍니다.


135-137: 셀 dequeue 실패 시 더 안전한 처리가 필요합니다

현재 빈 UITableViewCell을 반환하고 있어 예상치 못한 UI 동작을 일으킬 수 있습니다. 로깅이나 적절한 fallback 처리를 고려해주세요.


157-159: 의존성 해결 실패 시 더 우아한 에러 처리를 권장합니다

fatalError 사용은 프로덕션 환경에서 위험할 수 있습니다. 로깅과 함께 더 안전한 에러 처리 방식을 고려해주세요.

🧹 Nitpick comments (3)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (3)

117-117: TODO 주석 정리 권장

SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 설정 페이지 구현이 확정된 계획이라면 별도 이슈로 추적하고 TODO를 제거하는 것을 권장합니다.


117-117: TODO 주석 해결이 필요합니다

SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 설정 페이지 연결 계획이 있다면 구체적인 일정을 명시하거나, 당분간 구현 예정이 없다면 주석을 제거해주세요.


117-117: TODO 주석 정리를 권장합니다

SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 설정 페이지 구현이 계획되어 있다면 별도 이슈로 추적하거나, 명확한 설명 주석으로 변경하는 것을 고려해주세요.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d00b2f and 1ae7ba7.

⛔ Files ignored due to path filters (6)
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@1x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@3x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.png is excluded by !**/*.png
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1 hunks)
  • Projects/DataSource/Sources/Common/Error/UserError.swift (1 hunks)
  • Projects/DataSource/Sources/Repository/UserDataRepository.swift (1 hunks)
  • Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift (1 hunks)
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json (1 hunks)
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json (1 hunks)
  • Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1 hunks)
  • Projects/Presentation/Sources/Common/Extension/NSObject+.swift (1 hunks)
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1 hunks)
  • Projects/Presentation/Sources/Common/TabBarView.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/View/MypageView.swift (1 hunks)
  • Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json
  • Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
  • Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
  • Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json
  • Projects/DataSource/Sources/Common/Error/UserError.swift
  • Projects/Presentation/Sources/Common/Extension/NSObject+.swift
  • Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift
  • Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift
  • Projects/Presentation/Sources/Common/TabBarView.swift
  • Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift
  • Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift
🧰 Additional context used
🧠 Learnings (1)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (5)

Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.

Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.

Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.

Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.

Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.

🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/MyPage/View/MypageView.swift

[Warning] 117-117: TODOs should be resolved (- 추후 설정 페이지 연결)

(todo)

Projects/DataSource/Sources/Repository/UserDataRepository.swift

[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)

(todo)

🔇 Additional comments (10)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (2)

11-36: 깔끔한 Repository 구현

의존성 주입 패턴과 프로토콜 추상화를 잘 활용한 구현입니다. 키체인과 UserDefaults를 적절히 분리하여 사용하고, 일관된 에러 처리를 통해 안정성을 확보했습니다.


11-36: 사용자 데이터 접근 레이어 구현이 적절합니다

Repository 패턴을 통한 데이터 접근 로직이 잘 구현되어 있습니다. KeychainStorage와 UserDefaultsStorage를 적절히 활용하고 있으며, 에러 처리도 명확하게 되어 있습니다.

Projects/Presentation/Sources/MyPage/View/MypageView.swift (8)

108-113: SFSafariViewController 사용으로 UX 개선

이전 리뷰 피드백을 반영하여 UIApplication.shared.open 대신 SFSafariViewController를 사용하도록 개선되었습니다. 사용자가 앱을 벗어나지 않고 외부 콘텐츠를 볼 수 있어 더 나은 사용자 경험을 제공합니다.


13-119: 잘 구성된 MVVM 아키텍처

BaseViewController를 활용한 깔끔한 구조와 Combine을 통한 반응형 바인딩이 잘 구현되어 있습니다. Layout enum을 통한 상수 관리와 명확한 책임 분리가 돋보입니다.


108-113: SFSafariViewController 사용으로 개선되었습니다

이전 리뷰에서 제안된 SFSafariViewController 사용으로 변경되어 사용자가 앱을 벗어나지 않고도 웹 페이지를 볼 수 있게 되었습니다. 좋은 개선입니다.


13-40: 뷰 컨트롤러 구조와 초기화가 잘 구성되었습니다

BaseViewController를 상속받은 구조가 적절하며, Combine을 활용한 cancellables 관리와 의존성 주입을 통한 뷰모델 전달이 잘 구현되어 있습니다.


14-23: Layout 상수 관리가 체계적입니다

Layout enum을 통한 상수 관리가 잘 되어 있어 유지보수성과 가독성이 좋습니다. 특히 profileImageViewCornerRadius를 size의 절반으로 계산한 부분이 적절합니다.


8-8: SafariServices import 추가 - 좋은 개선사항입니다

이전 피드백을 반영하여 SFSafariViewController 사용을 위한 SafariServices import가 추가되었습니다. 앱 내에서 웹페이지를 보여주는 더 나은 사용자 경험을 제공할 수 있게 되었습니다.


44-45: Navigation 구조 개선 - 좋은 변경사항입니다

이전 피드백을 반영하여 커스텀 title label 대신 navigation title을 사용하도록 변경되었습니다. 다른 뷰들과의 일관성이 개선되었고 더 자연스러운 레이아웃을 제공합니다.


108-113: SFSafariViewController 사용으로 개선되었습니다

이전 피드백을 반영하여 UIApplication.shared.open 대신 SFSafariViewController를 사용하도록 변경되었습니다. 사용자가 앱을 벗어나지 않고도 웹 콘텐츠를 볼 수 있어 더 나은 사용자 경험을 제공합니다.

@choijungp choijungp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

띵푸루부 ~~ 👍🏻👍🏻👍🏻 짱 고생하셨습니다 !!!!

@taipaise
taipaise merged commit 21db344 into develop Jul 21, 2025
@taipaise
taipaise deleted the feat/mypage branch July 27, 2025 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants